P. Cortez, A. Cerdeira, F. Almeida, T. Matos and J. Reis. Modeling wine preferences by data mining from physicochemical properties. In Decision Support Systems, Elsevier, 47(4):547-553, 2009.
import pandas as pd
import numpy as np
def warn(*args, **kwargs):
pass
import warnings
warnings.warn = warn
import matplotlib.pyplot as plt
plt.style.use('ggplot')
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.calibration import CalibratedClassifierCV
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn import metrics
from sklearn.metrics import accuracy_score
from sklearn.metrics import plot_confusion_matrix
from joblib import dump, load
redWine = pd.read_csv("./data/winequality-red.csv",sep=";")
whiteWine = pd.read_csv("./data/winequality-white.csv",sep=";")
redWine.info()
whiteWine.info()
redWine.describe()
whiteWine.describe()
fig1, ax1 = plt.subplots(facecolor=(1, 1, 1),figsize=(20,5))
ax1.pie([redWine.size,whiteWine.size], labels=('Red', 'White'), autopct='%1.1f%%',shadow=True, startangle=90)
ax1.axis('equal')
fig1.suptitle('Proportion des vins selon leur couleur', fontsize=16)
plt.show()
whiteWine["type"]="White"
redWine["type"]="Red"
data = pd.concat([whiteWine,redWine],ignore_index=True)
data.insert(0,"count",1)
result = data[["quality","type","count"]].groupby(["quality","type"]).count()
result.reset_index(level=[0,1], inplace=True)
result=result.pivot("quality","type","count")
result["Red"] = result["Red"]/len(redWine)
result["White"] = result["White"]/len(whiteWine)
result.plot(kind='bar',figsize=(19.5,8),title= "Répartition moyenne des notes en fonction de la couleur du vin" )
plt.show()
whiteWine = whiteWine.drop(["type"],1)
redWine = redWine.drop(["type"],1)
Nous pouvons constater à travers les boîtes à moustache ci-dessous, qu'un grand nombre d'observations contiennent des valeurs aberrantes, qui peuvent faire référence à des erreurs dans les données ou à des cas spécifiques pouvant potentiellement biaiser notre modèle (elles peuvent également être dues au fait que l'ensemble de données soit petit et loin d'être exhaustive).
def showOutliers(white,red):
plt.figure(figsize=(20,20))
plt.suptitle('Boxplots de chaque feature mettant en évidence les valeurs aberrantes',fontsize=24)
for i in range(0,len(white.columns)):
plt.subplot(4,3,i+1)
plt.boxplot([white.iloc[:,i],red.iloc[:,i]])
plt.title(white.columns[i],fontsize=18)
showOutliers(whiteWine,redWine)
def getOutliersIndex(dataframe):
outliersIndex = set()
dataframe.reset_index(inplace=True,drop=True)
for i in range(0,len(dataframe.columns)):
p25 = np.percentile(dataframe.iloc[:,i],25)
p75 = np.percentile(dataframe.iloc[:,i],75)
iqr = p75 - p25
iqrXi = iqr*1.5
for j, val in enumerate(dataframe.iloc[:,i]):
if val < p25-iqrXi or val > p75 + iqrXi:
outliersIndex.add(j)
print("Nbr outliers : ",len(outliersIndex))
return outliersIndex
def dropOutliers(dataframe):
initialSize = len(dataframe)
cleanedData = dataframe.drop(getOutliersIndex(dataframe))
print(round((initialSize-len(cleanedData))*100/initialSize,1),"% of the lines will be deleted")
return cleanedData
cleaningWhite = dropOutliers(whiteWine)
cleaningRed = dropOutliers(redWine)
showOutliers(cleaningWhite,cleaningRed)
Les deux matrices de corrélation ci-dessous ne permettent pas de conclure à l'existence d'une très forte corrélation évidente entre :
Cependant, nous remarquons que la variable "alcohol" pourrait être la variable la plus importante pour la prédiction.
Nous pouvons observer à travers la matrice de corrélation ci-dessous (vins rouges):
corr = redWine.corr()
corr.style.background_gradient(cmap='coolwarm')
corr = whiteWine.corr()
corr.style.background_gradient(cmap='coolwarm')
import plotly.graph_objects as go
dataMean=redWine.groupby("quality").mean()
dataMean.reset_index(level=0, inplace=True)
quality = dataMean.quality.to_list()
fig = go.Figure()
for i,colName in enumerate(dataMean.columns[1:]):
if i ==0:
fig.add_trace(go.Bar(y=quality,x=dataMean[colName].to_list(),orientation='h',name=colName))
else:
fig.add_trace(go.Bar(y=quality,x=dataMean[colName].to_list(),orientation='h',name=colName,visible='legendonly'))
fig.update_layout(barmode='group',title="Influence des attributs sur la qualité du vin", xaxis_title="Active legend attribut(default = fixed acidity)",yaxis_title="Quality Wine")
fig.show()
def trainAndEvaluate(model,dataSet,withOutliers=False,TargetClass="quality"):
"""
Cette fonction permet de :
- withOutliers = True --> Supprimer les valeurs aberrantes (IQR).
- model = entraîner le modèle spécifié en paramètre.
- évaluer le modèle en utilisant la métrique de précision et la matrice de confusion.
"""
# séparer le jeu de données en variables explicatives et les variables expliquées.
if withOutliers:
X = dropOutliers(dataSet).drop(TargetClass, axis = 1)
y = dropOutliers(dataSet)[TargetClass]
else:
X = dataSet.drop(TargetClass, axis = 1)
y = dataSet[TargetClass]
# diviser le jeu de données en un jeu de formation et un jeu de test avec un échantillonnage stratifié pour garder la distribution initiale des classes
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.20, random_state = 1996,stratify=y)
# entrainer le modèle.
model.fit(X_train, y_train)
# prédiction du jeu de test
y_pred = model.predict(X_test)
# évaluation du modèle en utilisant la précision
print("Accuracy_score :",accuracy_score(y_pred,y_test))
# afficher la matrice de confusion
disp = plot_confusion_matrix(model, X_test, y_test,cmap=plt.cm.Blues)
disp.ax_.set_title("Confusion Matrix")
plt.show()
return model
Nous observons clairement à travers la matrice de corrélation ci-dessous que les classes ne sont pas correctement distribuées.
La solution est de supprimer les classes ayant un nombre insignifiant d’échantillons et cela soit avec un lissage par intervalle ou via la suppression des valeurs aberrantes.
# choix des hyperparamètres avec une recherche par grille.
calibrated_forest = CalibratedClassifierCV(base_estimator=RandomForestClassifier(n_estimators=500,random_state=1996))
param_grid = {'base_estimator__n_estimators':[400,500,600]}
rfModel = GridSearchCV(calibrated_forest, param_grid, cv=5)
trainAndEvaluate(rfModel,whiteWine,TargetClass="quality")
print("best_score of cross validation", rfModel.best_score_)
print("the best number of trees ", rfModel.best_params_)
rfModelWithOut = RandomForestClassifier(n_estimators=400, random_state=1996)
trainAndEvaluate(rfModelWithOut,whiteWine,withOutliers=True,TargetClass="quality")
def smoothingInterval(x):
if x > 6:
return 3
elif x < 6:
return 0
return 2
whiteWineSmoothed = whiteWine.copy()
whiteWineSmoothed["quality"] = whiteWine["quality"].map(smoothingInterval)
white_RF_Model_Smoothed = RandomForestClassifier(n_estimators=400, random_state=1996)
trainAndEvaluate(white_RF_Model_Smoothed,whiteWineSmoothed,withOutliers=False,TargetClass="quality")
varImportance = pd.DataFrame(white_RF_Model_Smoothed.feature_importances_.tolist(),index=whiteWineSmoothed.columns.to_list()[:-1],columns=["importance"])
varImportance.sort_values('importance')
expérimenter la suppression de l'attribut le moins informatif "fixed acidity":
white_RF_Model_Smoothed_select = RandomForestClassifier(n_estimators=400, random_state=1996)
trainAndEvaluate(white_RF_Model_Smoothed_select,(whiteWineSmoothed.drop(["fixed acidity"], axis = 1)),TargetClass="quality")
X = whiteWineSmoothed.drop(['quality'], axis = 1)
y = whiteWineSmoothed['quality']
# Normalisation des données
sc_x=StandardScaler()
X = sc_x.fit_transform(X)
# Choix du nombre idéal de dimensions à conserver
pca=PCA()
X_pca = pca.fit_transform(X)
plt.figure(figsize=(10,8))
plt.plot(np.cumsum(pca.explained_variance_ratio_), 'ro-')
plt.grid()
# transformation des données avec l'acp
pca_new = PCA(n_components=8)
whitePCAData = (pd.DataFrame(pca_new.fit_transform(X))).join(pd.DataFrame(y,columns=["quality"]))
rf_PCA_Smooth_white = RandomForestClassifier(n_estimators=400, random_state=1996)
trainAndEvaluate(rf_PCA_Smooth_white,pd.DataFrame(whitePCAData),TargetClass="quality")
print("__________________________________Random Forest____________________________________________")
print("Choix des hyperparamètres (n_estimators)")
calibrated_forest = CalibratedClassifierCV(base_estimator=RandomForestClassifier(n_estimators=500,random_state=1996))
param_grid = {'base_estimator__n_estimators':[400,500,600]}
rfModel = GridSearchCV(calibrated_forest, param_grid, cv=5)
trainAndEvaluate(rfModel,redWine,TargetClass="quality")
print("best_score of cross validation", rfModel.best_score_)
print("the best number of trees ", rfModel.best_params_)
nbrtree = rfModel.best_params_["base_estimator__n_estimators"]
print("_______________________________________Remove Outliers_______________________________________")
rfModelWithOut = RandomForestClassifier(n_estimators=nbrtree, random_state=1996)
trainAndEvaluate(rfModelWithOut,redWine,withOutliers=True,TargetClass="quality")
print("_______________________________________Smoothing Classe_______________________________________")
redWineSmoothed = redWine.copy()
redWineSmoothed["quality"] = redWine["quality"].map(smoothingInterval)
red_RF_Model_Smoothed = RandomForestClassifier(n_estimators=nbrtree, random_state=1996)
trainAndEvaluate(red_RF_Model_Smoothed,redWineSmoothed,withOutliers=False,TargetClass="quality")
print("__________________________________________ACP________________________________________________")
X = redWineSmoothed.drop(['quality'], axis = 1)
y = redWineSmoothed['quality']
# Normalisation des données
sc_x=StandardScaler()
X = sc_x.fit_transform(X)
# Choix du nombre idéal de dimensions à conserver
pca=PCA()
X_pca = pca.fit_transform(X)
plt.figure(figsize=(10,8))
plt.plot(np.cumsum(pca.explained_variance_ratio_), 'ro-')
plt.grid()
# transformation des données avec l'acp
pca_new = PCA(n_components=8)
redPCAData = (pd.DataFrame(pca_new.fit_transform(X))).join(pd.DataFrame(y,columns=["quality"]))
rf_PCA_Smooth_red = RandomForestClassifier(n_estimators=nbrtree, random_state=1996)
trainAndEvaluate(rf_PCA_Smooth_red,pd.DataFrame(redPCAData),TargetClass="quality")
J'ai choisi d'utiliser les deux modèles ci-dessous de manière à obtenir la plus grande précision.
Et ce, sans supprimer les valeurs aberrantes et sans effectuer une sélection d'attributs.
Car, malgré la possibile d'avoir une grande précision en réalisant cette série de prétriatements, nos modèles peuvent être sujets à un sur-apprentissage (faible en généralisation à cause de la taille infime de nos deux ensembles de données).
dump(white_RF_Model_Smoothed,"model/WhiteWineModel.pkl")
dump(red_RF_Model_Smoothed,"model/RedWineModel.pkl")
Il est également intéressant de tester d'autres modèles d'apprentissage automatique basés sur des réseaux de neurones qui peuvent potentiellement avoir une meilleure précision.